package org.paradigmshift.timelapse.stopwatch

import org.paradigmshift.timelapse.exceptions.TimeLapseException
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.mutable.SynchronizedSet
import scala.collection.mutable.HashSet
import java.util.Date
import org.paradigmshift.timelapse.actor.messages.Exit

case class SetActive( stopwatch:Stopwatch )

case class Add( subscribers:List[Actor] )
case class Remove( subscribers:List[Actor] )
case class Reset
case class Start
case class Started
case class GetStartDate
case class Pause
case class Paused
case class Time( seconds:Int )

object Stopwatch extends Actor {
  val SECONDS_IN_A_MINUTE = 60L
  val SECONDS_IN_AN_HOUR = 60L * SECONDS_IN_A_MINUTE
  
  var activeStopwatch:Option[Stopwatch] = None
  
  start()
  
  def act() {
    loop {
      react {
        case SetActive( stopwatch ) =>
          val asw = for { asw <- activeStopwatch; if ( asw != stopwatch ) } yield {
            asw ! Pause
            asw
          }
          if ( asw != stopwatch ) activeStopwatch = Option( stopwatch )
          reply()
        case Exit => exit
        case _ => throw new TimeLapseException( "Invalid message" )
      }
    }
  }
}

class Stopwatch extends Actor with Runnable {
  
  private val executorService = Executors.newSingleThreadScheduledExecutor()
  private var future:Option[ScheduledFuture[_]] = None
  private var started:Option[Date] = None
  private var elapsedTime = -1
  private var subscribers = List[Actor]()
  
  start()
  
  def act() {
    loop {
      react {
        case a:Add =>
          if ( !isRunning ) subscribers = subscribers ++ a.subscribers
          else throw new TimeLapseException( "Cannot add subscribers: stopwatch is running" )
        case Reset =>
          if ( !isRunning ) {
            started = None
            elapsedTime = -1
            subscribers.foreach( subscriber => subscriber ! Time( 0 ) )
          }
          else throw new TimeLapseException( "Cannot reset: stopwatch is running" )
        case r:Remove =>
          if ( !isRunning ) subscribers = subscribers -- r.subscribers
          else throw new TimeLapseException( "Cannot remove subscribers: stopwatch is running" )
        case Start =>
          if ( !isRunning ) {
            Stopwatch !? SetActive( this )
            if ( started == None ) started = Option( new Date )
            subscribers.foreach( subscriber => subscriber ! Started )
            future = Some( executorService.scheduleAtFixedRate( this, 0, 1, TimeUnit.SECONDS ) )
          }
        case Pause =>
          pauseStopwatch
        case GetStartDate => reply( started )
        case Exit =>
          pauseStopwatch
          exit
        case _ => throw new TimeLapseException( "Invalid message" )
      }
    }
  }
  
  def run() {
    elapsedTime += 1
    subscribers.foreach( subscriber => subscriber ! Time( elapsedTime ) )
  }
  
  private def pauseStopwatch() {
    for ( f <- future if isRunning ) yield f.cancel( false )
    subscribers.foreach( subscriber => subscriber ! Paused )
  }
  
  private def isRunning = future match {
    case Some( value ) => !future.get.isCancelled() || !future.get.isDone()
    case None => false
  }
}